#ifndef _LSUP_CORE_H
#define _LSUP_CORE_H

#include <ctype.h>
#include <dirent.h>
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <uuid/uuid.h>

#include "log.h"
#include "xxhash.h"


// Logging and debugging.

#ifdef DEBUG
// GDB breakpoints.
#include <signal.h>
#define BREAKPOINT raise (SIGINT)

#else
#define BREAKPOINT

#endif


#define LIKELY(x)       __builtin_expect(!!(x), true)
#define UNLIKELY(x)     __builtin_expect(!!(x), false)

#define LSUP_NS "urn:lsup:" /// Default LS namespace.

// TODO Cross-platform ramdisk path.
#define TMPDIR "/tmp"

#define KLEN sizeof(LSUP_Key)
#define DBL_KLEN sizeof(LSUP_DoubleKey)
#define TRP_KLEN sizeof(LSUP_TripleKey)
#define QUAD_KLEN sizeof(LSUP_QuadKey)

# define UUIDSTR_SIZE 37

/** @brief "NULL" triple, a value that is never user-provided.
 */
#define NULL_TRP {NULL_KEY, NULL_KEY, NULL_KEY}


/* * * RETURN CODES * * */

/**
 * 0 is success, positive integers (>88800) are warnings, and negative integers
 * (>-88900) are errors.
 */
typedef int LSUP_rc;

/** @brief Generic success return code.
 */
#define LSUP_OK             0

/** @brief No action taken.
 *
 * An attempt to create or update a resource was made, but the resource already
 * existed substantially in the same form, so no action took place. The caller
 * is expected to find the resource as though the action actually took place.
 * If this is returned from the iteration of multiple updates, it means that
 * none of the iterations produced a change in state.
 */
#define LSUP_NOACTION       88801

/** @brief No result yielded.
 *
 * A read operation returned no results. If an iterator is expected to be
 * created, it may be created empty.
 */
#define LSUP_NORESULT       88802

/** @brief Loop end.
 *
 * End of a loop was reached. This can be used in a while() or for()
 * loop as a terminating condition.
 */
#define LSUP_END            88803

/** @brief Conflict warning.
 * An attempt to create or update a resource was made, but the resource existed
 * with a different form or value. The caller should find the value of the
 * existing resource to be different than the one that was attempted to store.
 * If this is returned from the iteration of multiple updates, it means that
 * other resources in the loop may have changed state and the operation as a
 * whole completed successfully.
 */
#define LSUP_CONFLICT       88804

/*
 * NOTE When adding new warning codes, use a value larger than the last one
 * in the list. Also change LSUP_MAX_WARNING.
 */

#define LSUP_MIN_WARNING    LSUP_NOACTION
#define LSUP_MAX_WARNING    LSUP_CONFLICT

/** @brief Generic error return code.
 */
#define LSUP_ERROR          -88899

/** @brief TODO
 */
#define LSUP_PARSE_ERR      -88898

/** @brief TODO
 */
#define LSUP_VALUE_ERR      -88897

/** @brief TODO
 */
#define LSUP_TXN_ERR        -88896

/** @brief TODO
 */
#define LSUP_DB_ERR         -88895

/** @brief TODO
 */
#define LSUP_NOT_IMPL_ERR   -88894

/** @brief TODO
 */
#define LSUP_IO_ERR         -88893

/** @brief TODO
 */
#define LSUP_MEM_ERR        -88892

/** @brief Conflict error.
 *
 * A critical resource conflict happened and no resources were updated. If this
 * is returned from the iteration of multiple updates, it means that the
 * operation has been interrupted and any state change within the loop prior to
 * the error has been rolled back.
 */
#define LSUP_CONFLICT_ERR   -88891

/** @brief TODO
 */
#define LSUP_ENV_ERR        -88890

/*
 * NOTE When adding new error codes, use a value larger than the last one
 * in the list. Also change LSUP_MAX_ERROR.
 */

#define LSUP_MIN_ERROR      LSUP_ERROR
#define LSUP_MAX_ERROR      LSUP_ENV_ERR

// Hashing stuff.

#ifndef LSUP_HASH_SEED
/** @brief Seed used for all hashing. Compile-time configurable.
 */
#define LSUP_HASH_SEED 0
#endif

#define LSUP_HASH32(buf, size, seed) XXH32 (buf, size, seed)
#define LSUP_HASH64(buf, size, seed) XXH3_64bits_withSeed (buf, size, seed)
#define LSUP_HASH128(buf, size, seed) XXH3_128bits_withSeed (buf, size, seed)
// TODO Add 32-bit switch.
#define LSUP_HASH(buf, size, seed) LSUP_HASH64 (buf, size, seed)


extern char *warning_msg[], *error_msg[];

extern char *LSUP_root_path;


typedef XXH32_hash_t LSUP_Hash32;
typedef XXH64_hash_t LSUP_Hash64;
typedef XXH128_hash_t LSUP_Hash128;
// TODO Add 32-bit switch.
typedef LSUP_Hash64 LSUP_Hash;

typedef enum {
    LSUP_BOOL_UNION,
    LSUP_BOOL_SUBTRACTION,
    LSUP_BOOL_INTERSECTION,
    LSUP_BOOL_XOR,
} LSUP_bool_op;


typedef size_t LSUP_Key;
typedef LSUP_Key LSUP_DoubleKey[2];
typedef LSUP_Key LSUP_TripleKey[3];
typedef LSUP_Key LSUP_QuadKey[4];

typedef char uuid_str_t[UUIDSTR_SIZE];


/** @brief Make recursive directories.
 *
 * Modified from
 * https://gist.github.com/JonathonReinhart/8c0d90191c38af2dcadb102c4e202950
 */
LSUP_rc
mkdir_p (const char *path, mode_t mode);


/** @brief Remove a directory recursively, as in Unix "rm -r".
 *
 * @param path[in] Path of directory to remove.
 */
LSUP_rc
rm_r (const char *path);


/** @brief Return an error message for a return code.
 */
const char *
LSUP_strerror (LSUP_rc rc);


/** @brief Encode a code point using UTF-8.
 *
 * https://gist.github.com/MightyPork/52eda3e5677b4b03524e40c9f0ab1da5
 *
 * @author Ondřej Hruška <ondra@ondrovo.com>
 *
 * @license MIT
 *
 * @param out - output buffer (min 5 characters), will be 0-terminated
 * @param utf - code point 0-0x10FFFF
 * @return number of bytes on success, 0 on failure (also produces U+FFFD,
 *  which uses 3 bytes)
 */
inline int utf8_encode(const uint32_t utf, unsigned char *out)
{
  if (utf <= 0x7F) {
    // Plain ASCII
    out[0] = (char) utf;
    out[1] = 0;
    return 1;
  }
  else if (utf <= 0x07FF) {
    // 2-byte unicode
    out[0] = (char) (((utf >> 6) & 0x1F) | 0xC0);
    out[1] = (char) (((utf >> 0) & 0x3F) | 0x80);
    out[2] = 0;
    return 2;
  }
  else if (utf <= 0xFFFF) {
    // 3-byte unicode
    out[0] = (char) (((utf >> 12) & 0x0F) | 0xE0);
    out[1] = (char) (((utf >>  6) & 0x3F) | 0x80);
    out[2] = (char) (((utf >>  0) & 0x3F) | 0x80);
    out[3] = 0;
    return 3;
  }
  else if (utf <= 0x10FFFF) {
    // 4-byte unicode
    out[0] = (char) (((utf >> 18) & 0x07) | 0xF0);
    out[1] = (char) (((utf >> 12) & 0x3F) | 0x80);
    out[2] = (char) (((utf >>  6) & 0x3F) | 0x80);
    out[3] = (char) (((utf >>  0) & 0x3F) | 0x80);
    out[4] = 0;
    return 4;
  }
  else {
    // error - use replacement character
    out[0] = (char) 0xEF;
    out[1] = (char) 0xBF;
    out[2] = (char) 0xBD;
    out[3] = 0;
    return 0;
  }
}


/** @brief Log an error or warning for return codes that are not LSUP_OK.
 *
 * Note that, if used outside of the other macros below, care must be taken
 * to pass it an actual return code rather than an expression, otherwise the
 * expression will be evaluated multiple times.
 */
#define LOG_RC(rc) do {                                             \
    if ((rc) < 0) log_error (LSUP_strerror (rc));                   \
    else if ((rc) > 0) log_warn (LSUP_strerror (rc));               \
} while (0);

/// Error handling via goto.
#define CHECK(exp, marker) do {                                     \
    LSUP_rc _rc = (exp);                                            \
    LOG_RC(_rc);                                                    \
    if (UNLIKELY (_rc != LSUP_OK)) goto marker;                     \
} while (0);

/// Jump if rc is negative (skip warnings).
#define PCHECK(exp, marker) do {                                    \
    LSUP_rc _rc = (exp);                                            \
    LOG_RC(_rc);                                                    \
    if (UNLIKELY (_rc < LSUP_OK)) goto marker;                      \
} while (0);

/// Return rc if it is of LSUP_rc type and nonzero.
#define RCCK(exp) do {                                              \
    LSUP_rc _rc = (exp);                                            \
    LOG_RC(_rc);                                                    \
    if (UNLIKELY (_rc != LSUP_OK)) return _rc;                       \
} while (0);

/// Return rc if it is of LSUP_rc type and negative (=error)
#define PRCCK(exp) do {                                              \
    LSUP_rc _rc = (exp);                                            \
    LOG_RC(_rc);                                                    \
    if (UNLIKELY (_rc < LSUP_OK)) return _rc;                       \
} while (0);

/// Return NULL if RC is nonzero.
#define RCNL(exp) do {                                              \
    LSUP_rc _rc = (exp);                                            \
    LOG_RC(_rc);                                                    \
    if (UNLIKELY (_rc != LSUP_OK)) return NULL;                      \
} while (0);

/// Return NULL if RC is negative (=error)
#define PRCNL(exp) do {                                              \
    LSUP_rc _rc = (exp);                                            \
    LOG_RC(_rc);                                                    \
    if (UNLIKELY (_rc < LSUP_OK)) return NULL;                      \
} while (0);

/// Allocate one pointer with malloc and return rc if it fails.
#define MALLOC_GUARD(var, rc) do {                                  \
    (var) = malloc (sizeof *(var));                                 \
    if (UNLIKELY (var == NULL)) return (rc);                        \
} while (0);

/// Allocate one pointer with calloc and return rc if it fails.
#define CALLOC_GUARD(var, rc) do {                                  \
    (var) = calloc (1, sizeof *(var));                              \
    if (UNLIKELY (var == NULL)) return (rc);                        \
} while (0);

/*
#define MALLOC_GUARD_ME(var) MALLOC_GUARD((var), LSUP_MEM_ERR)      \
#define CALLOC_GUARD_ME(var) CALLOC_GUARD((var), LSUP_MEM_ERR)      \
#define MALLOC_GUARD_NL(var) MALLOC_GUARD((var), NULL)              \
#define CALLOC_GUARD_NL(var) CALLOC_GUARD((var), NULL)              \
*/

#endif  /* _LSUP_CORE_H */
